/*****************************************************************************
 * Copyright (c) 2007, Ping Identity
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ping Identity, nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY PING IDENTITY ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL PING IDENTITY BE LIABLE FOR ANY DIRECT, INDIRECT, 
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************/

#if 0 /* Headers */
{
#endif
#include "config.h"
#include "context_internal.h"
#include "saml_11.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/parserInternals.h>
#include <libxml/xmlschemas.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>

#ifndef XMLSEC_NO_XSLT
#include <libxslt/xslt.h>
#endif /* XMLSEC_NO_XSLT */

#include <xmlsec/xmlsec.h>
#include <xmlsec/xmltree.h>
#include <xmlsec/xmlenc.h>
#include <xmlsec/crypto.h>

/* include the binary data of the SAML assertion schema */
#include "oasis-sstc-saml-schema-assertion-1.1.h"
#include "xmldsig-core-schema.h"

#if 0 /* End Headers */
}
#endif

#if 0 /* Constants */
{
#endif
const xmlChar ATTR_MAJOR_VERSION[]		= "MajorVersion";
const xmlChar ATTR_MINOR_VERSION[]		= "MinorVersion";
const xmlChar ATTR_ISSUER[]			= "Issuer";
const xmlChar ATTR_NOT_BEFORE[]			= "NotBefore";
const xmlChar ATTR_NOT_ON_OR_AFTER[]		= "NotOnOrAfter";
const xmlChar ATTR_ASSERTION_ID[]		= "AssertionID";
const xmlChar ATTR_ATTRIBUTE_NAME[]		= "AttributeName";
const xmlChar ATTR_ATTRIBUTE_NAMESPACE[]	= "AttributeNamespace";

const xmlChar SELF_ATTR_ISSUER[]		= 
	"http://schemas.xmlsoap.org/ws/2005/05/identity/issuer/self";
const xmlChar NS_SAML_11[]			= "urn:oasis:names:tc:SAML:1.0:assertion";
const xmlChar NS_DSIG[]				= "http://www.w3.org/2000/09/xmldsig#";
const xmlChar URL_DSIG[]			= "http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd";
const xmlChar CONDITIONS[]			= "Conditions";
const xmlChar AUDIENCE_RESTRICTION_CONDITION[]	=
	"AudienceRestrictionCondition";
const xmlChar DO_NOT_CACHE_CONDITION[]		= "DoNotCacheCondition";
const xmlChar ATTRIBUTE_STATEMENT []		= "AttributeStatement";
const xmlChar ATTRIBUTE []			= "Attribute";
const xmlChar MODULUS[]				= "Modulus";

const xmlChar PATH_MODULUS[] 			=
"/saml:Assertion/ds:Signature/ds:KeyInfo/ds:KeyValue/ds:RSAKeyValue/ds:Modulus";
const xmlChar PRE_SAML[]			= "saml";
const xmlChar PRE_DS[]				= "ds";
#if 0 /* End Headers */
}
#endif

#if 0 /* Prototypes */
{
#endif
/*
 @function decrypt_doc
	Given a cryptographic context and an xml-encrypted document,
	replace the document with the decrypted contents. Decryption is done
	based on the private key in the context, meant to be the SSL private key.
 */
static bool decrypt_doc(struct ic_context_t *ctx, 
			xmlDocPtr encr_doc);
/*
 @function validate_saml11_token
	Given a saml 1.1 token, validate against the SAML 1.1 schema.
 */
static bool validate_saml11_token(struct ic_context_t *ctx,
				  xmlDocPtr token_doc);
/*
 @function validate_self_issued_assertion
	Given a saml 1.1 token, verify the issuer is correct according to the
	information card profile.
 */
static bool validate_self_issued_assertion(struct ic_context_t *ctx,
					   xmlDocPtr token_doc);

/*
 @function validate_saml11_conditions
	Given a saml 1.1 token, verify all of the conditions included.
 */
static bool validate_saml11_conditions(struct ic_context_t *ctx,
				   xmlDocPtr token_doc);
/*
 @function validate_saml11_signature
	Given a saml 1.1 token with an enveloped signature and included public key,
	validate the signature on the token.
 */
static bool validate_saml11_signature(struct ic_context_t *ctx,
									  xmlDocPtr token_doc);

/*
 @function extract_saml11_info
	Given a saml 1.1 token which has been validated, walk the token and report
	information via the attribute reporter callback.
 */
static void extract_saml11_info(struct ic_context_t *ctx,
								xmlDocPtr token_doc);
/*
 @function evaluate_time_conditions
	validate the specific notBefore, notOnOrAfter values within the conditions
	element
 */
static bool evaluate_time_conditions(struct ic_context_t *ctx,
									 xmlNodePtr conditions);
/*
 @function evaluate_child_conditions
	walk the child elements of the saml conditions, evaluating each. 
	AudienceRestriction is evaluated if present against the context,
	DoNotCacheCondition is ignored as the library does not cache or report the
	SAML 1.1 assertion. Other values will trigger a failure.
 */
static bool evaluate_child_conditions(struct ic_context_t *ctx,
									 xmlNodePtr conditions);
/*
 @function evaluate_saml11_replay
	get the replay value from the SAML 1.1 token and call the replay callback
 */
static bool evaluate_saml11_replay(struct ic_context_t *ctx,
								   xmlDocPtr token_doc);

/*
 @function extract_saml11_attributes
	Report the attribute values of the SAML 1.1 assertion to the attribute
	reporter callback
 */
static bool extract_saml11_attributes(struct ic_context_t *ctx,
									  xmlDocPtr token_doc);
/*
 @function extract_saml11_keyinfo_modulus
	Report the public key modulus as an attribute. This is used for 
	identification in conjunction with ppid if ppid is not treated as a
	shared secret.
 */
static bool extract_saml11_keyinfo_modulus(struct ic_context_t *ctx,
										   xmlDocPtr token_doc);
	
#if 0 /* End Prototypes */
}
#endif

xmlParserInputPtr xmlInternalSchemaEntityLoader	(const char * URL, 
						 const char * ID, 
						 xmlParserCtxtPtr context) {
if (strcmp(URL, (const char *)NS_SAML_11) == 0)
{
	return xmlNewStringInputStream(context, oasis_sstc_saml_schema_assertion_1_1);
}
else if (strcmp(URL, (const char *) URL_DSIG) == 0 || strcmp(URL, (const char *)NS_DSIG) == 0)
{
	return xmlNewStringInputStream(context, xmldsig_core_schema);
/*
	xmlParserInputBufferPtr buffer = xmlParserInputBufferCreateStatic(xmldsig_core_schema, sizeof(xmldsig_core_schema) - 1, XML_CHAR_ENCODING_UTF8);
	xmlParserCtxtPtr ctxt = xmlNewParserCtxt();
	return xmlNewIOInputStream(ctxt, buffer, XML_CHAR_ENCODING_UTF8);
*/
}
else
	return NULL;
}

bool saml11_process_token(ic_ctx ctx, const unsigned char *input, size_t input_len)
{

	xmlParserCtxtPtr	parser_ctx	= NULL;
	xmlDocPtr			encr_doc	= NULL;
	xmlSetExternalEntityLoader(xmlInternalSchemaEntityLoader);

	// Processing flow:
	// 1. load text into xml
	// 2. validate and decrypt encrypted token using key manager
	// 3. validate SAML message structure
	// 4. validate SAML message conditions
	// 5. validate signature
	// 6. walk SAML message, triggering events for internal structures and
	//    attribute statements
	
	// 1. load text into xml
	if (input_len > ctx->input_max_size)
	{
		log_reasonf(ctx, ERROR_LVL,
			 "Given information card is larger than the maximum size allowed by local security rules (%d > %d)",
			input_len, ctx->input_max_size);
		return false;
	}
	parser_ctx = xmlNewParserCtxt();
	if (parser_ctx == NULL)
	{
		log_error(ctx, "unable to initialize parser context");
		return false;
	}
	
	/* load the xmlToken */
	encr_doc = xmlCtxtReadMemory(parser_ctx, 
								 (const char*) input, (int) input_len, 
								 NULL, NULL, 0);
	if (encr_doc == NULL)
	{
		xmlErrorPtr err = xmlCtxtGetLastError(parser_ctx);
		log_reasonf(ctx, ERROR_LVL, 
					"input document parsing failed, non-wellformed XML: %s",
					(err != NULL) ?err->message : "null");
		xmlFreeParserCtxt(parser_ctx);
		return false;
	}
	
	/* 2. validate and decrypt encrypted token using key manager */
	if (!decrypt_doc(ctx, encr_doc /*, parser_ctx, xpath_ctx */))
	{
		/* assume error reporting from decrypt_doc */
		xmlFreeDoc(encr_doc);
		xmlFreeParserCtxt(parser_ctx);
		return false;		
	}
	
	/* 3. validate SAML message structure */
	if (!validate_saml11_token(ctx, encr_doc))
	{
		/* assume error reporting from validate_saml11_token */
		xmlFreeDoc(encr_doc);
		xmlFreeParserCtxt(parser_ctx);
		return false;
	}
	if (!evaluate_saml11_replay(ctx, encr_doc))
	{
		/* error reporting from evaluate method */
		xmlFreeDoc(encr_doc);
		xmlFreeParserCtxt(parser_ctx);
		return false;
	}
	/* 4. validate SAML message conditions */
	if (!validate_saml11_conditions( ctx, encr_doc))
	{
		/* assume error reporting from validate_saml11_conditions */
		xmlFreeDoc(encr_doc);
		xmlFreeParserCtxt(parser_ctx);
		return false;
	}
	
	if (!validate_self_issued_assertion(ctx, encr_doc))
	{
		xmlFreeDoc(encr_doc);
		xmlFreeParserCtxt(parser_ctx);
		return false;
	}
		
	
	/* 5. validate signature */
	if (!validate_saml11_signature(ctx, encr_doc))
	{
		/* assume error reporting from validate_saml11_signature */
		xmlFreeDoc(encr_doc);
		xmlFreeParserCtxt(parser_ctx);
		return false;
	}
	/* 6. walk SAML message, triggering events */
	extract_saml11_info(ctx, encr_doc);
	xmlFreeDoc(encr_doc);
	xmlFreeParserCtxt(parser_ctx);
	return true;
}

static bool decrypt_doc(struct ic_context_t *ctx, 
					  xmlDocPtr encr_doc) 
{
	xmlNodePtr root_element, sec_node;
	xmlSecEncCtxPtr encr_ctx;
	
	root_element = xmlDocGetRootElement(encr_doc);
	sec_node = xmlSecFindNode(root_element, xmlSecNodeEncryptedData, xmlSecEncNs);
    if(sec_node == NULL) 
	{
		log_error(ctx, "received token missing EncryptedData element");
		return false;
    }
	
    encr_ctx = xmlSecEncCtxCreate(ctx->key_manager);
    if(encr_ctx == NULL) {
        log_error(ctx, "failed to create encryption context");
		return false;
	}
	
	if((xmlSecEncCtxDecrypt(encr_ctx, sec_node) < 0) || 
	   (encr_ctx->result == NULL)) 
	{
        log_error(ctx, "decryption failed");
		xmlSecEncCtxDestroy(encr_ctx);
		return false;
	}
	if (encr_ctx->resultReplaced == 0)
	{		
		log_error(ctx, "decryption yielded unexpected binary data");
		xmlSecEncCtxDestroy(encr_ctx);
		return false;
	}
	xmlSecEncCtxDestroy(encr_ctx);
	return true;
}

/*
 @function saml11_validity_err_rep
	Report xmlsec errors into the logging callback
 */
static void	saml11_validity_err_rep	(void * ctx, 
									 const char * msg, 
									 ...)
{
	va_list va;
	va_start(va, msg);
	log_vreasonf((ic_ctx)ctx, ERROR_LVL, msg, va);
}

/*
 @function saml11_validity_warn_rep
 Report xmlsec warnings as informational into the logging callback
 */
static void saml11_validity_warn_rep (void *ctx,
										const char *msg,
										...)
{
	va_list va;
	va_start(va, msg);
	log_vreasonf((ic_ctx)ctx, INFO_LVL, msg, va);
}

static bool validate_saml11_token(struct ic_context_t *ctx,
								  xmlDocPtr token_doc)
{
	xmlSchemaParserCtxtPtr schema_ctx = 
		xmlSchemaNewMemParserCtxt((const char*)oasis_sstc_saml_schema_assertion_1_1,
								  sizeof(oasis_sstc_saml_schema_assertion_1_1));
	if (schema_ctx == NULL)
	{
		log_error(ctx, "unable to create schema parsing context");
		return false;
	}
	
	xmlSchemaPtr schema = xmlSchemaParse(schema_ctx);
	if (schema == NULL)
	{
		log_error(ctx, "unable to parse schema");
		xmlSchemaFreeParserCtxt(schema_ctx);
		return false;
	}
	
	xmlSchemaValidCtxtPtr validator = xmlSchemaNewValidCtxt(schema);
	if (validator == NULL)
	{
		log_error(ctx, "unable to create schema validator context");
		xmlSchemaFree(schema);
		xmlSchemaFreeParserCtxt(schema_ctx);
		return false;
	}

	xmlSchemaSetValidErrors(validator,
				saml11_validity_err_rep,
				saml11_validity_warn_rep,
				ctx);
	
	int result = xmlSchemaValidateDoc(validator, token_doc);
	xmlSchemaFreeValidCtxt(validator);
	xmlSchemaFree(schema);
	xmlSchemaFreeParserCtxt(schema_ctx);
	if (result != 0)
	{
		log_error(ctx, "schema validation of SAML 1.1 token failed");
		return false;		
	}
	
	/* Now check major and minor versions */
	xmlNodePtr root = xmlDocGetRootElement(token_doc);
	xmlChar * MajorVersion = xmlGetNoNsProp(root, ATTR_MAJOR_VERSION);
	if (xmlStrcmp(MajorVersion, (xmlChar*)"1") != 0)
	{
		log_error(ctx, "unexpected SAML major version received");
		xmlFree(MajorVersion);
		return false;
	}
	xmlFree(MajorVersion);

	xmlChar * MinorVersion = xmlGetNoNsProp(root, ATTR_MINOR_VERSION);
	if (xmlStrcmp(MinorVersion, (xmlChar*)"1") != 0)
	{
		log_error(ctx, "unexpected SAML minor version received");
		xmlFree(MinorVersion);
		return false;
	}
	xmlFree(MinorVersion);
	return true;
}

static bool validate_self_issued_assertion(struct ic_context_t *ctx,
										   xmlDocPtr token_doc)
{
	xmlNodePtr root = xmlDocGetRootElement(token_doc);
	xmlChar *issuer = xmlGetNoNsProp(root, ATTR_ISSUER);
	if (xmlStrcmp(SELF_ATTR_ISSUER, issuer) != 0)
	{
		log_error(ctx, "SAML message not self-issued, is this a managed identity?");
		xmlFree(issuer);
		return false;
	}
	xmlFree(issuer);
	return true;
}
static bool validate_saml11_conditions(struct ic_context_t *ctx,
									   xmlDocPtr token_doc)
{
	xmlNodePtr root = xmlDocGetRootElement(token_doc);
	xmlNodePtr conditions = NULL;
	for (conditions = root->children; conditions != NULL; 
		   conditions = conditions->next)
	{
		if (conditions->type == XML_ELEMENT_NODE &&
			xmlStrcmp(conditions->name, CONDITIONS) == 0 &&
			xmlStrcmp(conditions->ns->href, NS_SAML_11) == 0 )
			break;
	}
	if (conditions == NULL)
	{
		log_error(ctx, "conditions element missing");
		return false;
	}
	
	if (!evaluate_time_conditions(ctx, conditions))
	{
		// assume evaluate time conditions returned error
		return false;
	}
	if (!evaluate_child_conditions(ctx, conditions))
	{
		// assume evaluate child conditions returned error
		return false;
	}
	return true;
}

static bool evaluate_time_conditions(struct ic_context_t *ctx,
									 xmlNodePtr conditions)
{
	/* a bit of magic; the time format is descending order, so if we produce a
	 string with the current time in it that matches the format, we can do a
	 straight string comparison */
	/* WORKAROUND: The current microsoft CardSpace selector will send self-issued identities with
	   NotBefore set to the current time, rather than allowing for clock drift in the opposite
	   direction. For this reason, we create a representation of five minutes in the future for
	   comparing against NotBefore */
	xmlChar format[] = "YYYY-MM-DDTHH:MM:SS.mmmZ";
	xmlChar now[sizeof(format)], nowPlusFive[sizeof(format)];

	xmlChar *notBefore = xmlGetNoNsProp(conditions, ATTR_NOT_BEFORE);
	xmlChar *notOnOrAfter = xmlGetNoNsProp(conditions, ATTR_NOT_ON_OR_AFTER);
	time_t timev = ic_ctx_get_current_time(ctx);
	
	struct tm timeinf,timeinfPlusFive;
	gmtime_r(&timev, &timeinf);
	timev += 60 * 5;
	gmtime_r(&timev, &timeinfPlusFive);

	snprintf((char*)now, sizeof(now), 
			 "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.000Z",
			 timeinf.tm_year + 1900, 
			 timeinf.tm_mon + 1, 
			 timeinf.tm_mday,
			 timeinf.tm_hour,
			 timeinf.tm_min,
			 timeinf.tm_sec);
	snprintf((char*)nowPlusFive, sizeof(nowPlusFive), 
			 "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.000Z",
			 timeinfPlusFive.tm_year + 1900, 
			 timeinfPlusFive.tm_mon + 1, 
			 timeinfPlusFive.tm_mday,
			 timeinfPlusFive.tm_hour,
			 timeinfPlusFive.tm_min,
			 timeinfPlusFive.tm_sec);
        
	bool notBeforeMet = (xmlStrcmp(nowPlusFive, notBefore)  >= 0);
	bool notOnOrAfterMet = (xmlStrcmp(now, notOnOrAfter) < 0);
	if (!notOnOrAfterMet)
	{
		log_reasonf(ctx, ERROR_LVL, "token has expired (now: %s , NotOnOrAfter: %s)", now, notOnOrAfter);
		xmlFree(notBefore);
		xmlFree(notOnOrAfter);
		return false;
	}
	if (!notBeforeMet)
	{
		log_reasonf(ctx, ERROR_LVL, "token has not yet been issued, possibly due to clock drift (now (+ 5 minutes): %s, notBefore: %s)", nowPlusFive, notBefore);
		xmlFree(notBefore);
		xmlFree(notOnOrAfter);
		return false;
	}
	xmlFree(notBefore);
	xmlFree(notOnOrAfter);
	return true;
}

static bool evaluate_child_conditions(struct ic_context_t *ctx,
									  xmlNodePtr conditions)
{
	for(xmlNodePtr child = conditions->children; child != NULL; child = child->next)
	{
		if (child->type != XML_ELEMENT_NODE)
			continue;
		if (xmlStrcmp(child->ns->href, NS_SAML_11) != 0)
		{
			log_reasonf(ctx, ERROR_LVL, "unknown condition encountered: {%s}:%s", child->ns->href, child->name);
			return false;
		}
		if (xmlStrcmp(child->name, AUDIENCE_RESTRICTION_CONDITION) == 0)
		{
			bool matched = false;
			for (xmlNodePtr audience = child->children;audience != NULL; audience = audience->next)
			{
				if (audience->type != XML_ELEMENT_NODE)
				{
					continue;
				}
				if (audience->children != NULL && ic_ctx_is_in_audience(ctx, audience->children->content))
				{
					matched = true;
					break;
				}
			}
			if (!matched)
			{
				log_error(ctx, "failed AudienceRestrictionCondition validation;"
						  " message is not addressed to the local installation");
				return false;
			}
			continue;
		}
		else if (xmlStrcmp(child->name, DO_NOT_CACHE_CONDITION) == 0)
		{
			/* we do not cache the assertion itself, so ignore */
			continue;
		}
		log_reasonf(ctx, ERROR_LVL,
					"condition '%s' unable to be processed, SAML token is indeterminate",
					child->name);
		return false;
	}
	return true;
}

static bool evaluate_saml11_replay(struct ic_context_t *ctx,
								   xmlDocPtr token_doc)
{
	xmlNodePtr root = xmlDocGetRootElement(token_doc);
	xmlChar *assertionId = xmlGetNoNsProp(root, ATTR_ASSERTION_ID);
	if (!ctx->detector(ctx, assertionId, -1, ctx->detector_context))
	{
		log_reasonf(ctx, ERROR_LVL, "replay detection failure for AssertionID '%s'", assertionId);
		xmlFree(assertionId);
		return false;
	}
	xmlFree(assertionId);
	return true;
}

static bool validate_saml11_signature(struct ic_context_t *ctx,
									  xmlDocPtr token_doc)
{
	xmlNodePtr root = xmlDocGetRootElement(token_doc);
	xmlNodePtr sig = xmlSecFindNode(root, xmlSecNodeSignature, xmlSecDSigNs);
	if (sig == NULL)
	{
		log_error(ctx, "SAML token signature not detected");
		return false;
	}
	xmlSecKeysMngrPtr keys_mgr = xmlSecKeysMngrCreate();
	if (xmlSecCryptoAppDefaultKeysMngrInit(keys_mgr) < 0)
	{
		log_error(ctx, "unable to initialize key manager");
		xmlSecKeysMngrDestroy(keys_mgr);
		return false;
	}
	xmlSecDSigCtxPtr dsig_ctx = xmlSecDSigCtxCreate(keys_mgr);
	if (dsig_ctx == NULL)
	{
		log_error(ctx, "failed to create digital signature verification context");
		xmlSecKeysMngrDestroy(keys_mgr);
		return false;
	}
	
	/* register AssertionID as an ID field, since we had to strip such info from the schema */
	xmlAttrPtr attr = xmlHasProp(root, ATTR_ASSERTION_ID);
	/* get the attribute (id) value */
	xmlChar *name = xmlNodeListGetString(token_doc, attr->children, 1);
	xmlAddID(NULL, token_doc, name, attr);
	xmlFree(name);

	/* extract the public key from KeyInfo */
	const xmlChar KEY_INFO [] = "KeyInfo";
	xmlSecKeyInfoCtxPtr keyinfo_ctx = xmlSecKeyInfoCtxCreate(NULL);
	xmlNodePtr keyinfo = xmlSecFindNode(root, KEY_INFO, xmlSecDSigNs);
	xmlSecKeyPtr key = xmlSecKeysMngrGetKey(keyinfo, keyinfo_ctx);

	if (xmlSecCryptoAppDefaultKeysMngrAdoptKey(keys_mgr, key) < 0)
	{
		log_error(ctx, "unable to import public key");
		xmlSecKeyInfoCtxDestroy(keyinfo_ctx);
		xmlSecDSigCtxDestroy(dsig_ctx);
		xmlSecKeysMngrDestroy(keys_mgr);
		return false;
	}
	
	if (xmlSecDSigCtxVerify(dsig_ctx, sig) < 0)
	{
		log_error(ctx, "signature validation failed");
		xmlSecKeyInfoCtxDestroy(keyinfo_ctx);
		xmlSecDSigCtxDestroy(dsig_ctx);
		xmlSecKeysMngrDestroy(keys_mgr);
		return false;
	}
	if (dsig_ctx->status != xmlSecDSigStatusSucceeded)
	{
		log_error(ctx, "digital signature is invalid");
		xmlSecKeyInfoCtxDestroy(keyinfo_ctx);
		xmlSecDSigCtxDestroy(dsig_ctx);
		xmlSecKeysMngrDestroy(keys_mgr);
		return false;
	}
	xmlSecKeyInfoCtxDestroy(keyinfo_ctx);
	xmlSecDSigCtxDestroy(dsig_ctx);
	xmlSecKeysMngrDestroy(keys_mgr);
	return true;
}

// TODO validate subject conf as 'sender'
static void extract_saml11_info(struct ic_context_t *ctx,
								xmlDocPtr token_doc)
{
	extract_saml11_attributes(ctx, token_doc);
	extract_saml11_keyinfo_modulus(ctx, token_doc);
}


static bool extract_saml11_attributes(struct ic_context_t *ctx,
									  xmlDocPtr token_doc)
{
	// "/saml:Assertion/saml:AttributeStatement/saml:Attribute/saml:AttributeValue/*"
	xmlNodePtr assertion = xmlDocGetRootElement(token_doc);
	xmlNodePtr attributeStmt = NULL;
	for (xmlNodePtr child = assertion->children; child != NULL; child = child->next)
	{
		if (child->type != XML_ELEMENT_NODE)
			continue;
		if (xmlStrcmp(child->ns->href, NS_SAML_11) != 0 ||
			xmlStrcmp(child->name, ATTRIBUTE_STATEMENT) != 0)
			continue;
		attributeStmt = child;
		break;
	}
	if (attributeStmt == NULL)
	{
		log_error(ctx, "unable to find SAML AttributeStatement");
		return false;
	}
	for (xmlNodePtr attr = attributeStmt->children; attr != NULL; attr = attr->next)
	{
		if (attr->type != XML_ELEMENT_NODE)
			continue;
		if (xmlStrcmp(attr->ns->href, NS_SAML_11) != 0 ||
			xmlStrcmp(attr->name, ATTRIBUTE) != 0)
			continue;
		
		xmlChar *name = xmlGetNoNsProp(attr, ATTR_ATTRIBUTE_NAME);
		xmlChar *ns = xmlGetNoNsProp(attr, ATTR_ATTRIBUTE_NAMESPACE);
		xmlChar *value = xmlNodeGetContent(attr);
		ctx->attr_reporter(ctx, name, ns, value, ctx->attr_context);
		xmlFree(name);
		xmlFree(ns);
		xmlFree(value);
	}
	return true;
}

static bool extract_saml11_keyinfo_modulus(struct ic_context_t *ctx,
								   xmlDocPtr token_doc)
{
	xmlXPathContextPtr xpath_ctx = xmlXPathNewContext(token_doc);
	xmlXPathRegisterNs(xpath_ctx, PRE_SAML, NS_SAML_11);
	xmlXPathRegisterNs(xpath_ctx, PRE_DS, NS_DSIG);
	xmlXPathObjectPtr result = xmlXPathEval(PATH_MODULUS, xpath_ctx);
	if (result->type != XPATH_NODESET)
	{
		log_error(ctx, "error processing xpath expression");
		xmlXPathFreeContext(xpath_ctx);
		xmlXPathFreeObject(result);
		return false;
	}
	xmlNodeSetPtr nodeset = result->nodesetval;
	if (nodeset->nodeNr != 1)
	{
		log_error (ctx, "multiple matches for xpath expression");
		xmlXPathFreeContext(xpath_ctx);
		xmlXPathFreeObject(result);
		return false;
	}
	xmlNodePtr modulus = *nodeset->nodeTab;
	xmlChar * content = xmlNodeGetContent(modulus);
	ctx->attr_reporter(ctx, MODULUS, NS_DSIG, content, ctx->attr_context);
	xmlFree(content);
	xmlXPathFreeContext(xpath_ctx);
	xmlXPathFreeObject(result);
	return true;
}
